﻿//////////////////////////////////////////////////////////////////////////////////////////////////////
//Name: Bartosz
//FullName: Adamczewski
//Project: InternalClasses
//Component: InternalClasses
//StartDate: 2009-08-17 19:29:04
//EndDate: 2009-08-17 19:29:04
//Description: The implementation of InternalClasses component.
//Rights:  Copyright (c) Bartosz Adamczewski
////////////////////////////////////////////////////////////////////////////////////////////////////

#region imports.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Specialized;
using System.Reflection.Emit;
using System.Reflection;

#endregion

namespace InternalClasses.Dynamic
{
    /// <summary>
    /// The Dynamic Method class.
    /// </summary>
    public class DynamicMethod : DynamicBase
    {
        private static readonly HybridDictionary dynamicMethodCache = new HybridDictionary();

        /// <summary>
        /// Clears the Dynamic Cache.
        /// </summary>
        public static void ClearDynamicCache()
        {
            dynamicMethodCache.Clear();
        }

        /// <summary>
        /// Invokes the Method.
        /// </summary>
        /// <param name="sourceMethod">sourceMethod.</param>
        /// <returns>InternalClasses.Dynamic.ObjectInvoker.</returns>
        public static ObjectInvoker InvokeMethod(MethodInfo sourceMethod)
        {
            if (dynamicMethodCache[sourceMethod] == null)
            {
                System.Reflection.Emit.DynamicMethod dynamicMethod = new System.Reflection.Emit.DynamicMethod(string.Empty, typeof(object), new Type[] { typeof(object), typeof(object[]) }, sourceMethod.DeclaringType.Module);

                ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
                CreateMethodCall(sourceMethod, ilGenerator, false);

                ObjectInvoker invoker = (ObjectInvoker)dynamicMethod.CreateDelegate(typeof(ObjectInvoker));

                lock (dynamicMethodCache)
                {
                    dynamicMethodCache[sourceMethod] = invoker;
                }

                return invoker;
            }
            else
            {
                return (ObjectInvoker)dynamicMethodCache[sourceMethod];
            }
        }

        /// <summary>
        /// Invokes the Injected Method.
        /// </summary>
        /// <param name="sourceMethod">sourceMethod.</param>
        /// <param name="injectedMethod">injectedMethod.</param>
        /// <param name="injectionType">injectionType.</param>
        /// <returns>InternalClasses.Dynamic.ObjectInvoker.</returns>
        public static ObjectInvoker InvokeInjectedMethod(MethodInfo sourceMethod, MethodInfo injectedMethod, InjectionType injectionType)
        {
            return InvokeInjectedMethod(sourceMethod, injectedMethod, injectionType, new object[] {});
        }

        /// <summary>
        /// Invokes the Injected Method.
        /// </summary>
        /// <param name="sourceMethod">sourceMethod.</param>
        /// <param name="injectedMethod">injectedMethod.</param>
        /// <param name="injectionType">injectionType.</param>
        /// <returns>InternalClasses.Dynamic.ObjectInvoker.</returns>
        public static ObjectInvoker InvokeInjectedMethod(MethodInfo sourceMethod, MethodInfo injectedMethod, InjectionType injectionType, object[] externalParameters)
        {
            if (injectedMethod.GetParameters().Length < 2)
            {
                throw new DynamicException("Injection method signature wrong");
            }

            System.Reflection.Emit.DynamicMethod dynamicMethod = new System.Reflection.Emit.DynamicMethod(string.Empty, typeof(object), new Type[] { typeof(object), typeof(object[]) }, sourceMethod.DeclaringType.Module);

            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();

            int paramsLength = injectedMethod.GetParameters().Length;
            bool hasExtenral = (paramsLength > 1);

            CreateArray(ilGenerator, externalParameters, 0);

            if (injectionType == InjectionType.BeforeSource)
            {
                CallInjectedMethod(ilGenerator, injectedMethod, hasExtenral);
            }

            CreateMethodCall(sourceMethod, ilGenerator, true);

            if (injectionType == InjectionType.AfterSource)
            {
                CallInjectedMethod(ilGenerator, injectedMethod, hasExtenral);
            }

            if (sourceMethod.ReturnType != typeof(void))
            {
                ilGenerator.DeclareLocal(typeof(object));
                ilGenerator.Emit(OpCodes.Stloc_2);
                ilGenerator.Emit(OpCodes.Ldloc_2);
            }
            else
            {
                ilGenerator.Emit(OpCodes.Ldnull);
            }

            ilGenerator.Emit(OpCodes.Ret);

            ObjectInvoker invoker = (ObjectInvoker)dynamicMethod.CreateDelegate(typeof(ObjectInvoker));

            return invoker;
        }

    }
}
